// 6.2 参数传递
/**
 * 如果形参是引用类型（参见2.3.1节，第45页），它将绑定到对应的实参上；否则，将实参的值拷贝后赋给形参。
 * 即引用传递和值传递。
 */
#include <iostream>
#include <initializer_list>
#include <iterator>
using std::cin, std::cout, std::endl;
using std::initializer_list;
using std::string;
using std::begin, std::end;

// 和vector不一样的是，initializer_list对象中的元素永远是常量值，我们无法改变initializer_list对象中元素的值
void error_msg(initializer_list<string> i1)
{
    for(auto beg = i1.begin(); beg!= i1.end(); ++beg)
    {
        cout << *beg << " ";
    }
    cout << endl;
}

// 使用标记指定数组长度
void printa(const char *cp)
{
    if(cp) // 若cp不是一个空指针
    {
        while (*cp) // 只要指针所指的字符不是空字符
        {
            cout << *cp++ << endl; // 输出当前字符并将指针向前移动一个位置
        }
    }
}

// 使用标准库规范
void printa(const int *beg,const int *end)
{
    // 输出beg和end之间所有元素
    while(beg != end)
    {
        cout << *beg++ << endl;
    }
}

// 显示传递一个表示数组大小的形参
// const int ia[] 等价于 const int* ia
void printa(const int ia[],size_t size) // size表示数组的大小
{
    for(size_t i = 0; i!=size; ++ i)
    {
        cout << ia[i] << endl;
    }
}

int main(int argc, char const *argv[])
{
    // 6.2.1 传值参数
    int n = 0; // int类型的初始变量
    int i = n; // i是n的值的副本
    i = 42;    // 改变i的值，n的值不变

    // 指针形参
    // 当执行指针拷贝操作时，拷贝的是指针的值。
    // 拷贝之后，两个指针是不同的指针。
    // 因为指针使我们可以间接地访问它所指的对象，所以通过指针可以修改它所指对象的值

    // 熟悉C的程序员常常使用指针类型的形参访问函数外部的对象。在C++语言中，建议使用引用类型的形参替代指针。

    // 6.2.2 传引用参数
    /**
     * 使用引用避免拷贝
     * 拷贝大的类类型对象或者容器对象比较低效，甚至有的类类型（包括IO类型在内）根本就不支持拷贝操作。
     * 当某种类型不支持拷贝操作时，函数只能通过引用形参访问该类型的对象。
     *
     * 举个例子，我们准备编写一个函数比较两个string对象的长度。
     * 因为string对象可能会非常长，所以应该尽量避免直接拷贝它们，这时使用引用形参是比较明智的选择。
     * 又因为比较长度无须改变string对象的内容，所以把形参定义成对常量的引用
     *
     * 一个函数只能返回一个值，然而有时函数需要同时返回多个值，引用形参为我们一次返回多个结果提供了有效的途径。
     */

    // 6.2.3 const形参和实参
    /**
     * 当用实参初始化形参时会忽略掉形参的顶层const。
     * 换句话说，形参的顶层const被忽略掉了。当形参有顶层const时，传给它常量对象或者非常量对象都是可以的
     * 关于顶层const和底层const，参考2.4 const限定符这一章节。
     *
     * 我们可以使用非常量初始化一个底层const对象，但是反过来不行；同时一个普通的引用必须用同类型的对象初始化
     *
     * 尽量使用常量引用
     */
    int ccc(int &i);
    int ccc(const int &i);
    int i = 0;
    int ci = 1;
    ccc(i);
    ccc(ci);

    // 6.2.4 数组形参
    /**
     * 因为不能拷贝数组，所以我们无法以值传递的方式使用数组参数。
     * 因为数组会被转换成指针，所以当我们为函数传递一个数组时，实际上传递的是指向数组首元素的指针。
     *
     * 因为数组是以指针的形式传递给函数的，所以一开始函数并不知道数组的确切尺寸，调用者应该为此提供一些额外的信息。
     * 管理指针形参有三种常用的技术:
     * 1.使用标记指定数组长度（管理数组实参的一种方法是要求数组本身包含一个结束标记，使用这种方法的典型示例是C风格字符串）
     * 2.使用标准库规范（使用标准库规范管理数组实参的第二种技术是传递指向数组首元素和尾后元素的指针）
     * 3.显式传递一个表示数组大小的形参（第三种管理数组实参的方法是专门定义一个表示数组大小的形参）
     *
     * 数组引用的形参：C++语言允许将变量定义成数组的引用（参见3.5.1节，第101页），基于同样的道理，形参也可以是数组的引用。
     */   
    int arr[] = {0, 1, 2, 3, 4, 5};
    int *p = arr; // p为指向arr数组的首元素的指针
    cout << *p << endl;
    int(&array)[6] = arr; // 定义对数组的引用，array是对arr的引用
    int *m[10];           // m是含有10个int型指针的数组
    int(*m1)[10];         // m1是个指针，指向含有10个int型元素的数组
    // 尽管形式不同，但这三个print函数是等价的
    // 每个print函数都有一个const int*类型的形参
    void print(const int[]); // 可以看出来，函数的意图是作用于一个数组
    void print(const int*); // 等价于上面的print
    void print(const int[10]); // 等价于上面的print，这里的维度表示我们期望数组含有多少元素，实际不一定
    // 当编译器处理对print函数的调用时，只检查传入的参数是否是const int＊类型
    int i = 0, j[2] = {0,1};
    print(&i); // 正确，&i的类型是int*
    print(j); // 正确，j转换成int*并指向j[0]

    // 6.2.6 含有可变形参的函数
    /**
     * 为了编写能处理不同数量实参的函数，C++11新标准提供了两种主要的方法：
     * 1.如果所有的实参类型相同，可以传递一个名为initializer_list的标准库类型；
     * 2.如果实参的类型不同，我们可以编写一种特殊的函数，也就是所谓的可变参数模板，关于它的细节将在16.4节（第618页）介绍。
     * 
     * C++还有一种特殊的形参类型（即省略符），可以用它传递可变数量的实参。
     * 本节将简要介绍省略符形参，不过需要注意的是，这种功能一般只用于与C函数交互的接口程序。
     * 
     * initializer_list是一种标准库类型，用于表示某种特定类型的值的数组（参见3.5节，第101页）。
     * initializer_list类型定义在同名的头文件中，它提供的操作如表6.1所示。
     * 和vector一样，initializer_list也是一种模板类型（参见3.3节，第86页）。定义initializer_list对象时，必须说明列表中所含元素的类型
     * 
     * 省略符形参应该仅仅用于C和C++通用的类型。特别应该注意的是，大多数类类型的对象在传递给省略符形参时都无法正确拷贝。
     * 省略符形参只能出现在形参列表的最后一个位置，如：void foo(parm_list,...);
     */
    error_msg({"1","2","c"}); // 可变形参用{}表示


    return 0;
}
